﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data.Entity;

using Whoever.Framework.Struct;
using Whoever.Framework.Base;
using System.Data.Entity.Validation;

namespace Whoever.Patterns.Data
{
    public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : BusinessEntity, new()
    {
        private readonly DbContext _context;
        private DbSet _dbSet;
        private readonly QueryFactory<TEntity> _queryFactory;

        public Repository(DbContext context)
        {
            _context = context;
            _dbSet = context.Set<TEntity>();
            _queryFactory = new QueryFactory<TEntity>(Context);
        }

        #region PROPERTIES

        public QueryFactory<TEntity> QueryFactory
        {
            get
            {
                return _queryFactory;
            }
        }

        protected DbContext Context
        {
            get
            {
                return _context;
            }
        }

        protected DbSet DBSet
        {
            get
            {
                return _dbSet;
            }
        }

        #endregion PROPERTIES

        #region METHODS

        public virtual TEntity GetById(TEntity entity)
        {
            return (TEntity)DBSet.Find(entity.Id);
        }

        public virtual TEntity GetByKeys(ViewModel<TEntity> model)
        {
            return this.QueryFactory.GetByKeys(model).Single();
        }

        public virtual TEntity GetByKeys(TEntity entity)
        {
            return this.QueryFactory.GetByKeys(entity).Single();
        }

        public virtual IEnumerable<TEntity> GetAll()
        {
            return this.QueryFactory.GetAll().ToList();
        }

        public virtual IEnumerable<TEntity> GetAll(ref Pager pager)
        {
            return this.QueryFactory.GetAll(ref pager).ToList();
        }

        public virtual IEnumerable<TEntity> GetAllRelated()
        {
            return this.QueryFactory.GetAllRelated().ToList();
        }

        public virtual IEnumerable<TEntity> GetAllRelated(ref Pager pager)
        {
            return this.QueryFactory.GetAllRelated(ref pager).ToList();
        }

        public virtual IEnumerable<TEntity> GetJustRelated()
        {
            return this.QueryFactory.GetJustRelated().ToList();
        }

        public virtual IEnumerable<TEntity> GetFiltered(ViewModel<TEntity> model)
        {
            return this.QueryFactory.GetFiltered(model).ToList();
        }

        public virtual IEnumerable<TEntity> GetFiltered(ViewModel<TEntity> model, ref Pager pager)
        {
            return this.QueryFactory.GetFiltered(model, ref pager).ToList();
        }

        public virtual IEnumerable<TEntity> GetFiltered(TEntity entity)
        {
            return this.QueryFactory.GetFiltered(entity).ToList();
        }

        public virtual IEnumerable<TEntity> GetFiltered(TEntity entity, ref Pager pager)
        {
            return this.QueryFactory.GetFiltered(entity, ref pager).ToList();
        }

        public virtual IEnumerable<TEntity> GetFiltered(Filter filter)
        {
            return this.QueryFactory.GetFiltered(filter).ToList();
        }

        public virtual IEnumerable<TEntity> GetFiltered(Filter filter, ref Pager pager)
        {
            return this.QueryFactory.GetFiltered(filter, ref pager).ToList();
        }

        public virtual IEnumerable<TEntity> GetFilteredRelated(ViewModel<TEntity> model)
        {
            return this.QueryFactory.GetFilteredRelated(model).ToList();
        }

        public virtual IEnumerable<TEntity> GetFilteredRelated(ViewModel<TEntity> model, ref Pager pager)
        {
            return this.QueryFactory.GetFilteredRelated(model, ref pager).ToList();
        }

        public virtual int Insert(TEntity item)
        {
            try
            {
                //item.Id = Guid.NewGuid().ToString();
                DBSet.Add(item);
                Context.SaveChanges();

                return item.Id;
            }
            catch (DbEntityValidationException ex)
            {
                throw ex;
            }
        }

        public virtual void Insert(IEnumerable<TEntity> items)
        {
            try
            {
                foreach (var item in items)
                {
                    this.Insert(item);
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw ex;
            }
        }

        public virtual void Update(TEntity item)
        {
            try
            {
                if (Context.Entry(item).State == EntityState.Detached)
                {
                    var contextItem = (TEntity)DBSet.Find(item.Id);

                    if (contextItem != null)
                        DBSet.Local.Clear();

                    DBSet.Attach(item);
                }

                Context.Entry(item).State = EntityState.Modified;
                Context.SaveChanges();
            }
            catch (DbEntityValidationException ex)
            {
                throw ex;
            }
        }

        public virtual void Update(IEnumerable<TEntity> items)
        {
            try
            {
                foreach (var item in items)
                {
                    this.Update(item);
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw ex;
            }
        }

        public virtual void Delete(TEntity item)
        {
            try
            {
                if (Context.Entry(item).State == EntityState.Detached)
                {
                    var contextItem = (TEntity)DBSet.Find(item.Id);

                    if (contextItem == null)
                        DBSet.Attach(item);
                    else
                        item = contextItem;
                }

                Context.Entry(item).State = EntityState.Deleted;
                DBSet.Remove(item);
                Context.SaveChanges();
            }
            catch (DbEntityValidationException ex)
            {
                throw ex;
            }
        }

        public virtual void Delete(IEnumerable<TEntity> items)
        {
            try
            {
                foreach (var item in items)
                {
                    this.Delete(item);
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw ex;
            }
        }

        public virtual void RollBack()
        {
            var changedEntries = Context.ChangeTracker.Entries().Where(x => x.State != EntityState.Unchanged).ToList();

            foreach (var entry in changedEntries.Where(x => x.State == EntityState.Modified))
            {
                entry.CurrentValues.SetValues(entry.OriginalValues);
                entry.State = EntityState.Unchanged;
            }

            foreach (var entry in changedEntries.Where(x => x.State == EntityState.Added))
            {
                entry.State = EntityState.Detached;
            }

            foreach (var entry in changedEntries.Where(x => x.State == EntityState.Deleted))
            {
                entry.State = EntityState.Unchanged;
            }
        }

        public virtual void UndoChanges(TEntity item)
        {
            var entry = Context.Entry(item);

            entry.CurrentValues.SetValues(entry.OriginalValues);
            entry.State = EntityState.Unchanged;
        }

        public virtual void UndoChanges(IEnumerable<TEntity> items)
        {
            try
            {
                foreach (var item in items)
                {
                    this.UndoChanges(item);
                }
            }
            catch (DbEntityValidationException ex)
            {
                throw ex;
            }
        }

        #endregion
    }
}
